{********************************************************************}
{                                                                    }
{           Developer Express Visual Component Library               }
{           ExpressLayoutControl common routines                     }
{                                                                    }
{           Copyright (c) 2001-2014 Developer Express Inc.           }
{           ALL RIGHTS RESERVED                                      }
{                                                                    }
{   The entire contents of this file is protected by U.S. and        }
{   International Copyright Laws. Unauthorized reproduction,         }
{   reverse-engineering, and distribution of all or any portion of   }
{   the code contained in this file is strictly prohibited and may   }
{   result in severe civil and criminal penalties and will be        }
{   prosecuted to the maximum extent possible under the law.         }
{                                                                    }
{   RESTRICTIONS                                                     }
{                                                                    }
{   THIS SOURCE CODE AND ALL RESULTING INTERMEDIATE FILES            }
{   (DCU, OBJ, DLL, ETC.) ARE CONFIDENTIAL AND PROPRIETARY TRADE     }
{   SECRETS OF DEVELOPER EXPRESS INC. THE REGISTERED DEVELOPER IS    }
{   LICENSED TO DISTRIBUTE THE EXPRESSLAYOUTCONTROL AND ALL          }
{   ACCOMPANYING VCL CONTROLS AS PART OF AN EXECUTABLE PROGRAM       }
{   ONLY.                                                            }
{                                                                    }
{   THE SOURCE CODE CONTAINED WITHIN THIS FILE AND ALL RELATED       }
{   FILES OR ANY PORTION OF ITS CONTENTS SHALL AT NO TIME BE         }
{   COPIED, TRANSFERRED, SOLD, DISTRIBUTED, OR OTHERWISE MADE        }
{   AVAILABLE TO OTHER INDIVIDUALS WITHOUT EXPRESS WRITTEN CONSENT   }
{   AND PERMISSION FROM DEVELOPER EXPRESS INC.                       }
{                                                                    }
{   CONSULT THE END USER LICENSE AGREEMENT FOR INFORMATION ON        }
{   ADDITIONAL RESTRICTIONS.                                         }
{                                                                    }
{********************************************************************}

unit dxLayoutDragAndDrop;

{$I cxVer.inc}

interface

uses
  Windows, Messages, Classes, Controls, Graphics,
  cxClasses, cxControls, cxGraphics, dxLayoutControl, dxLayoutContainer;

type
  TdxLayoutDragSource = (dsControl, dsCustomizeFormAvailableItems, dsCustomizeFormTreeViewItems);

  TdxLayoutCustomDragAndDropController = class;

  { TdxLayoutCustomDragAndDropObject }

  TdxLayoutCustomDragAndDropObject = class(TcxDragAndDropObject)
  private
    FContainer: TdxLayoutContainer;
    FSourceItem: TdxCustomLayoutItem;
    FStartDragPoint: TPoint;
  protected
    procedure Modified;
    //
    property Container: TdxLayoutContainer read FContainer;
  public
    constructor Create(AControl: TcxControl); override;
    procedure Init(ASource: TdxLayoutDragSource; ASourceItem: TdxCustomLayoutItem; const P: TPoint); virtual;
    //
    property SourceItem: TdxCustomLayoutItem read FSourceItem write FSourceItem;
  end;

  { TdxLayoutDragAndDropObject }

  TdxLayoutDragAndDropObject = class(TdxLayoutCustomDragAndDropObject)
  private
    FController: TdxLayoutCustomDragAndDropController;
    FSource: TdxLayoutDragSource;
    FSourceItemBounds: TRect;
    function GetDestinationItem: TdxCustomLayoutItem;
  protected
    function CreateController: TdxLayoutCustomDragAndDropController; virtual;
    procedure DirtyChanged; override;
    function GetDragAndDropCursor(Accepted: Boolean): TCursor; override;

    property Controller: TdxLayoutCustomDragAndDropController read FController;
    property Source: TdxLayoutDragSource read FSource write FSource;
    property SourceItemBounds: TRect read FSourceItemBounds;
  public
    constructor Create(AControl: TcxControl); override;
    destructor Destroy; override;
    procedure BeginDragAndDrop; override;
    procedure DragAndDrop(const P: TPoint; var Accepted: Boolean); override;
    procedure EndDragAndDrop(Accepted: Boolean); override;
    procedure Init(ASource: TdxLayoutDragSource; ASourceItem: TdxCustomLayoutItem; const P: TPoint); override;

    function CanDrop: Boolean;
    property DestinationItem: TdxCustomLayoutItem read GetDestinationItem;
  end;

  { TdxLayoutCustomSizingDragAndDropObject }

  TdxLayoutCustomSizingDragAndDropObject = class(TdxLayoutCustomDragAndDropObject)
  protected
    procedure RestoreSize; virtual;
  public
    procedure EndDragAndDrop(Accepted: Boolean); override;
  end;

  { TdxLayoutSizingDragAndDropObject }

  TdxLayoutSizingDragAndDropObject = class(TdxLayoutCustomSizingDragAndDropObject)
  private
    FOriginalBounds: TRect;
    FOriginalSize: TSize;
    FMarkerIndex: Integer;
  protected
    procedure RestoreSize; override;

    property MarkerIndex: Integer read FMarkerIndex;
  public
    constructor Create(AControl: TcxControl); override;
    destructor Destroy; override;

    procedure BeginDragAndDrop; override;
    procedure DragAndDrop(const P: TPoint; var Accepted: Boolean); override;
    procedure Init(ASource: TdxLayoutDragSource; ASourceItem: TdxCustomLayoutItem; const P: TPoint); override;
  end;

  { TdxLayoutSplitterDragAndDropObject }

  TdxLayoutSizingStrategy = (lssLeft, lssRight, lssClient, lssNone);

  TdxLayoutSplitterDragAndDropObject = class(TdxLayoutCustomSizingDragAndDropObject)
  private
    FParentItem: TdxLayoutGroup;
    FLeftItem: TdxCustomLayoutItem;
    FRightItem: TdxCustomLayoutItem;
    FOriginalSizes: array of TPoint;
    FSizingStrategy: TdxLayoutSizingStrategy;
    FLeftItemSize: Integer;
    FRightItemSize: Integer;
    FPrevPoint: TPoint;
  protected
    procedure RestoreSize; override;

    function GetWorkAlign(AViewInfo: TdxCustomLayoutItemViewInfo): TdxLayoutAlignHorz; virtual; abstract;
    function GetItemMaxSize(AViewInfo: TdxCustomLayoutItemViewInfo): Integer; virtual; abstract;
    function GetItemMinSize(AViewInfo: TdxCustomLayoutItemViewInfo): Integer; virtual; abstract;
    function GetItemSize(AViewInfo: TdxCustomLayoutItemViewInfo): Integer; virtual; abstract;
    function GetSignificantValue(const P: TPoint): Integer; virtual; abstract;
    procedure SetItemSize(AViewInfo: TdxCustomLayoutItemViewInfo; ASize: Integer); virtual; abstract;
  public
    procedure DragAndDrop(const P: TPoint; var Accepted: Boolean); override;
    procedure Init(ASource: TdxLayoutDragSource; ASourceItem: TdxCustomLayoutItem; const P: TPoint); override;
  end;

  { TdxLayoutHSplitterDragAndDropObject }

  TdxLayoutHSplitterDragAndDropObject = class(TdxLayoutSplitterDragAndDropObject)
  protected
    function GetWorkAlign(AViewInfo: TdxCustomLayoutItemViewInfo): TdxLayoutAlignHorz; override;
    function GetItemMaxSize(AViewInfo: TdxCustomLayoutItemViewInfo): Integer; override;
    function GetItemMinSize(AViewInfo: TdxCustomLayoutItemViewInfo): Integer; override;
    function GetItemSize(AViewInfo: TdxCustomLayoutItemViewInfo): Integer; override;
    function GetSignificantValue(const P: TPoint): Integer; override;
    procedure SetItemSize(AViewInfo: TdxCustomLayoutItemViewInfo; ASize: Integer); override;
  end;

  { TdxLayoutVSplitterDragAndDropObject }

  TdxLayoutVSplitterDragAndDropObject = class(TdxLayoutSplitterDragAndDropObject)
  protected
    function GetWorkAlign(AViewInfo: TdxCustomLayoutItemViewInfo): TdxLayoutAlignHorz; override;
    function GetItemMaxSize(AViewInfo: TdxCustomLayoutItemViewInfo): Integer; override;
    function GetItemMinSize(AViewInfo: TdxCustomLayoutItemViewInfo): Integer; override;
    function GetItemSize(AViewInfo: TdxCustomLayoutItemViewInfo): Integer; override;
    function GetSignificantValue(const P: TPoint): Integer; override;
    procedure SetItemSize(AViewInfo: TdxCustomLayoutItemViewInfo; ASize: Integer); override;
  end;

  { TdxLayoutDragAndDropHelper }

  TdxLayoutDragAndDropHelper = class(TObject)
  private
    FContainer: TdxLayoutContainer;
    FDragItem: TdxCustomLayoutItem;
    FMouseDownPos: TPoint;
    procedure BeginDragAndDrop(ASource: TdxLayoutDragSource);
    function CanBeginDragAndDrop(X, Y: Integer): Boolean;
    function GetControl: TcxControl;
    function GetDragAndDropObject: TdxLayoutDragAndDropObject;
  protected
    property Control: TcxControl read GetControl;
    property DragAndDropObject: TdxLayoutDragAndDropObject read GetDragAndDropObject;
  public
    constructor Create(AContainer: TdxLayoutContainer); virtual;

    procedure InitializeDragItem(AItem: TdxCustomLayoutItem; X, Y: Integer);
    procedure Reset;
    procedure TryBeginDragAndDrop(X, Y: Integer; ASource: TdxLayoutDragSource);

    property Container: TdxLayoutContainer read FContainer;
    property DragItem: TdxCustomLayoutItem read FDragItem;
  end;

  TdxLayoutItemInsertionKind = (ikNone, ikLeft, ikTop, ikRight, ikBottom, ikInside);

  { TdxLayoutCustomDragAndDropController }

  TdxLayoutCustomDragAndDropController = class
  private
    FDestinationContainer: TdxLayoutContainer;
    FDestinationGroup: TdxLayoutGroup;
    FDestinationItem: TdxCustomLayoutItem;
    FDestinationPoint: TPoint;

    FTargetItem: TdxCustomLayoutItem;

    FDragImage: TcxDragImage;
    FDragImageOffset: TPoint;
    FDragImagePoint: TPoint;
    FDropAreaPart: TdxLayoutDropAreaPart;
    FHitTest: TdxCustomLayoutHitTest;
    FNeedUpdateDestinationImage: Boolean;
    FOwner: TdxLayoutDragAndDropObject;
    FSource: TdxLayoutDragSource;
    FSourceItemBounds: TRect;

    FDestinationImage: TcxDragImage;

    function GetContainer: TdxLayoutContainer;
    function GetSourceItem: TdxCustomLayoutItem;
    procedure SetDropAreaPart(Value: TdxLayoutDropAreaPart);
    procedure SetDestinationContainer(Value: TdxLayoutContainer);
    procedure SetDestinationGroup(Value: TdxLayoutGroup);
    procedure SetDestinationItem(Value: TdxCustomLayoutItem);
    procedure SetTargetItem(Value: TdxCustomLayoutItem);
  protected
    procedure Changed;
    procedure CreateDragImage;
    procedure CreateDestinationImage;
    procedure DestroyDestinationImage;

    procedure DetermineAreaPart; virtual;
    procedure DetermineTargetItem; virtual;
    procedure DetermineDestinationItem; virtual;
    procedure DetermineDestinationGroup; virtual;
    procedure DoDrop(ALayoutAction: TdxLayoutActionType); virtual;
    function FindDestLayoutContainer(const P: TPoint): TdxLayoutContainer;
    function GetAreaPart(ADestinationItem: TdxCustomLayoutItem): TdxLayoutDropAreaPart; virtual;
    function GetDestinationGroup(ADestinationItem: TdxCustomLayoutItem): TdxLayoutGroup; virtual;
    function GetDestinationImageBounds: TRect; virtual;
    function GetDestinationPosition: Integer;
    function GetFittedRect(const ARect: TRect): TRect;
    procedure PaintDragImage;
    procedure PaintDestinationImageBackground; virtual;
    procedure PaintDestinationImageContent; virtual;
    procedure PaintDestinationImage;
    procedure UpdateDestinationImage; virtual;
    procedure ResetDragAndDropObjects;
    procedure ShowDragImage;
    procedure UpdateStates; virtual;
    procedure TargetItemChanged; virtual;

    property DropAreaPart: TdxLayoutDropAreaPart read FDropAreaPart write SetDropAreaPart;
    property DestinationContainer: TdxLayoutContainer read FDestinationContainer write SetDestinationContainer;
    property DestinationGroup: TdxLayoutGroup read FDestinationGroup write SetDestinationGroup;
    property DestinationItem: TdxCustomLayoutItem read FDestinationItem write SetDestinationItem;
    property TargetItem: TdxCustomLayoutItem read FTargetItem write SetTargetItem;
    property Container: TdxLayoutContainer read GetContainer;
    property Owner: TdxLayoutDragAndDropObject read FOwner;
    property Source: TdxLayoutDragSource read FSource write FSource;
    property SourceItem: TdxCustomLayoutItem read GetSourceItem;
    property SourceItemBounds: TRect read FSourceItemBounds;
  public
    constructor Create(AOwner: TdxLayoutDragAndDropObject); virtual;
    destructor Destroy; override;
    procedure Calculate(const P: TPoint; var Accepted: Boolean);
    function CanDrop: Boolean;
    function CanRemove: Boolean;
    procedure Drop(Accepted: Boolean);
    function GetCursor(Accepted: Boolean): TCursor;
    procedure Invalidate;
  end;

  { TdxLayoutDragAndDropController }

  TdxLayoutSingleDragAndDropController = class(TdxLayoutCustomDragAndDropController)
  protected
    function GetDestinationImageBounds: TRect; override;
  end;

  { TdxLayoutItemDropPlaceWindow }

  TdxLayoutItemDropPlaceWindow = class(TCustomControl)
  private
    FInControl: Boolean;
    FIsActive: Boolean;
    FDestinationItem: TdxCustomLayoutItem;

    procedure WMNCHitTest(var Message: TWMNCHitTest); message WM_NCHITTEST;
    procedure SetInControl(const Value: Boolean);
    procedure SetIsActive(Value: Boolean);
  protected
    procedure CreateParams(var Params: TCreateParams); override;
    procedure CreateWindowHandle(const Params: TCreateParams); override;
    procedure Paint; override;

    property InControl: Boolean read FInControl write SetInControl;
  end;

  { TdxLayoutItemDropPlaceWindows }

  TdxLayoutItemDropPlaceWindows = class(TcxObjectList)
  private
    FVisible: Boolean;
    function GetItem(Index: Integer): TdxLayoutItemDropPlaceWindow;
    procedure SetVisible(const Value: Boolean);

    function GetActivePlace: TdxLayoutItemDropPlaceWindow;
    procedure SetActivePlace(Value: TdxLayoutItemDropPlaceWindow);

    property ActivePlace: TdxLayoutItemDropPlaceWindow read GetActivePlace write SetActivePlace;
  public
    procedure Add(const R: TRect; ADestinationItem: TdxCustomLayoutItem);
    procedure Clear; override;

    property Items[Index: Integer]: TdxLayoutItemDropPlaceWindow read GetItem; default;
    property Visible: Boolean read FVisible write SetVisible;
  end;

  { TdxLayoutDragAndDropControllerEx }

  TdxLayoutMultiDragAndDropController = class(TdxLayoutCustomDragAndDropController)
  private
    FDropPlaces: TdxLayoutItemDropPlaceWindows;
    FAreaPartCode: Integer;

    function GetAreaPartCode: Integer;
    function GetDropPlaceWindow(const P: TPoint): TdxLayoutItemDropPlaceWindow;

    function GetScreenRect(const R: TRect): TRect;
    procedure MoveRectsApart(ADirection: TcxDirection; const ABaseRect: TRect; var AMovedRect: TRect); overload;
    procedure MoveRectsApart(const ABaseRect: TRect; var AMovedRect: TRect); overload;
  protected
    function GetDestinationImageBounds: TRect; override;
    procedure PaintDestinationImageContent; override;
    procedure UpdateDestinationImage; override;

    procedure TargetItemChanged; override;
    procedure DetermineAreaPart; override;
    procedure DetermineTargetItem; override;
    procedure DetermineDestinationItem; override;
    procedure DetermineDestinationGroup; override;

    property DropPlaces: TdxLayoutItemDropPlaceWindows read FDropPlaces;
  public
    constructor Create(AOwner: TdxLayoutDragAndDropObject); override;
    destructor Destroy; override;
  end;

function dxLayoutDragAndDropObject: TdxLayoutDragAndDropObject;
function dxLayoutSizingDragAndDropObject: TdxLayoutSizingDragAndDropObject;

implementation

uses
  Types, SysUtils, Forms, Math, cxLibraryConsts, cxGeometry;

const
  dxLayoutSelectionBorderDefaultColor: TColor = $BD8753;
  dxLayoutDestinationColor = $D0D0EE;
  dxLayoutDestinationBorderColor = $000059;

  dxCustomizeFormHitTestCodes = [htCustomizeForm, htAvailableItems, htTreeViewItems];

var
  FLayoutDragAndDropObject: TdxLayoutDragAndDropObject;
  FLayoutSizingDragAndDropObject: TdxLayoutSizingDragAndDropObject;

type
  TdxCustomLayoutItemAccess = class(TdxCustomLayoutItem);
  TdxCustomLayoutItemViewInfoAccess = class(TdxCustomLayoutItemViewInfo);
  TdxLayoutContainerAccess = class(TdxLayoutContainer);
  TdxLayoutGroupAccess = class(TdxLayoutGroup);
  TdxLayoutGroupViewInfoAccess = class(TdxLayoutGroupViewInfo);
  TdxLayoutSizeOptionsAccess = class(TdxLayoutSizeOptions);

function dxLayoutDragAndDropObject: TdxLayoutDragAndDropObject;
begin
  Result := FLayoutDragAndDropObject;
end;

function dxLayoutSizingDragAndDropObject: TdxLayoutSizingDragAndDropObject;
begin
  Result := FLayoutSizingDragAndDropObject;
end;

{ TdxLayoutCustomDragAndDropObject }

constructor TdxLayoutCustomDragAndDropObject.Create(AControl: TcxControl);
begin
  inherited Create(AControl);
  FContainer := (AControl as IdxLayoutContainer).GetContainer;
end;

procedure TdxLayoutCustomDragAndDropObject.Init(ASource: TdxLayoutDragSource;
  ASourceItem: TdxCustomLayoutItem; const P: TPoint);
begin
  FSourceItem := ASourceItem;
  FStartDragPoint := P;
end;

procedure TdxLayoutCustomDragAndDropObject.Modified;
begin
  Container.Modified;
end;

{ TdxLayoutDragAndDropObject }

function TdxLayoutDragAndDropObject.CanDrop: Boolean;
begin
  Result := Controller.CanDrop;
end;

constructor TdxLayoutDragAndDropObject.Create(AControl: TcxControl);
begin
  inherited Create(AControl);
  FLayoutDragAndDropObject := Self;
  FController := CreateController;
end;

destructor TdxLayoutDragAndDropObject.Destroy;
begin
  FLayoutDragAndDropObject := nil;
  FreeAndNil(FController);
  inherited Destroy;
end;


function TdxLayoutDragAndDropObject.CreateController: TdxLayoutCustomDragAndDropController;
begin
  case Container.DragDropMode of
    ddmDefault: Result := TdxLayoutSingleDragAndDropController.Create(Self);
    ddmMultiChoice: Result := TdxLayoutMultiDragAndDropController.Create(Self);
  else
    Result := nil;
  end;
end;

procedure TdxLayoutDragAndDropObject.DirtyChanged;
begin
  inherited DirtyChanged;
  Controller.Invalidate;
end;

function TdxLayoutDragAndDropObject.GetDestinationItem: TdxCustomLayoutItem;
begin
  Result := Controller.DestinationItem;
end;

function TdxLayoutDragAndDropObject.GetDragAndDropCursor(Accepted: Boolean): TCursor;
begin
  Result := Controller.GetCursor(Accepted);
end;

procedure TdxLayoutDragAndDropObject.BeginDragAndDrop;
begin
  inherited;
  if not TdxLayoutContainerAccess((Control as TdxCustomLayoutControl).Container).IsStandardCustomization then
  begin
    TdxCustomLayoutItemAccess(SourceItem).SelectComponent;
    TdxLayoutContainerAccess((Control as TdxCustomLayoutControl).Container).QuickCustomization := True;
  end;

  Controller.CreateDragImage;
end;

procedure TdxLayoutDragAndDropObject.DragAndDrop(const P: TPoint; var Accepted: Boolean);
begin
  Controller.Calculate(P, Accepted);
  inherited;
end;

procedure TdxLayoutDragAndDropObject.EndDragAndDrop(Accepted: Boolean);
begin
  Controller.Drop(Accepted);
  TdxLayoutContainerAccess((Control as TdxCustomLayoutControl).Container).QuickCustomization := False;
  inherited;
end;

procedure TdxLayoutDragAndDropObject.Init(ASource: TdxLayoutDragSource;
  ASourceItem: TdxCustomLayoutItem; const P: TPoint);
begin
  inherited;
  Container.Update;
  Source := ASource;
end;

{ TdxLayoutCustomSizingDragAndDropObject }

procedure TdxLayoutCustomSizingDragAndDropObject.EndDragAndDrop(Accepted: Boolean);
begin
  if Accepted then
    Modified
  else
    RestoreSize;
end;

procedure TdxLayoutCustomSizingDragAndDropObject.RestoreSize;
begin
// do nothing
end;

{ TdxLayoutSizingDragAndDropObject }

constructor TdxLayoutSizingDragAndDropObject.Create(AControl: TcxControl);
begin
  inherited Create(AControl);
  FLayoutSizingDragAndDropObject := Self;
end;

destructor TdxLayoutSizingDragAndDropObject.Destroy;
begin
  FLayoutSizingDragAndDropObject := nil;
  inherited Destroy;
end;

procedure TdxLayoutSizingDragAndDropObject.BeginDragAndDrop;
begin
  inherited BeginDragAndDrop;
  if not Container.IsDesigning then
    Container.SaveToUndo;
end;

procedure TdxLayoutSizingDragAndDropObject.DragAndDrop(const P: TPoint; var Accepted: Boolean);
var
  XC, YC: Integer;
begin
  if SourceItem.AlignHorz in [ahCenter] then
    XC := 2
  else
    XC := 1;

  if SourceItem.AlignVert in [avCenter] then
    YC := 2
  else
    YC := 1;

  if MarkerIndex in [0, 6, 7] then
    SourceItem.Width := cxRectWidth(FOriginalBounds) + XC * (FStartDragPoint.X - P.X);
  if MarkerIndex in [2, 3, 4] then
    SourceItem.Width := cxRectWidth(FOriginalBounds) + XC * (P.X - FStartDragPoint.X);
  if MarkerIndex in [0, 1, 2] then
    SourceItem.Height := cxRectHeight(FOriginalBounds) + YC * (FStartDragPoint.Y - P.Y);
  if MarkerIndex in [4, 5, 6] then
    SourceItem.Height := cxRectHeight(FOriginalBounds) + YC * (P.Y - FStartDragPoint.Y);
end;

procedure TdxLayoutSizingDragAndDropObject.Init(ASource: TdxLayoutDragSource; ASourceItem: TdxCustomLayoutItem; const P: TPoint);
begin
  inherited;
  FOriginalSize := cxSize(ASourceItem.Width, ASourceItem.Height);
  FOriginalBounds := ASourceItem.ViewInfo.Bounds;
  FMarkerIndex := TdxCustomLayoutItemViewInfoAccess(ASourceItem.ViewInfo).GetMarkerIndex(P);
end;

procedure TdxLayoutSizingDragAndDropObject.RestoreSize;
begin
  if Control.IsDesigning then
  begin
    SourceItem.Width := FOriginalSize.cx;
    SourceItem.Height := FOriginalSize.cy;
  end
  else
    Container.CancelLastUndo;
end;

{ TdxLayoutSplitterDragAndDropObject }

procedure TdxLayoutSplitterDragAndDropObject.DragAndDrop(const P: TPoint; var Accepted: Boolean);

  function GetNewLeftItemSize(ADelta: Integer): Integer;
  begin
    Result := FLeftItemSize + ADelta;
  end;

  function GetNewRightItemSize(ADelta: Integer): Integer;
  begin
    Result := FRightItemSize - ADelta;
  end;

  procedure CorrectDelta(var ADelta: Integer);
  begin
    if FLeftItem <> nil then
    begin
      if GetNewLeftItemSize(ADelta) < GetItemMinSize(FLeftItem.ViewInfo) then
        ADelta := GetItemMinSize(FLeftItem.ViewInfo) - FLeftItemSize
      else
        if GetNewLeftItemSize(ADelta) > GetItemMaxSize(FLeftItem.ViewInfo) then
          ADelta := GetItemMaxSize(FLeftItem.ViewInfo) - FLeftItemSize;
    end;
    if FRightItem <> nil then
    begin
      if GetNewRightItemSize(ADelta) < GetItemMinSize(FRightItem.ViewInfo) then
        ADelta := FRightItemSize - GetItemMinSize(FRightItem.ViewInfo)
      else
        if GetNewRightItemSize(ADelta) > GetItemMaxSize(FRightItem.ViewInfo) then
          ADelta := FRightItemSize - GetItemMaxSize(FRightItem.ViewInfo);
    end;
  end;

  function CanResize(ADelta: Integer): Boolean;
  begin
    Result := True;
    if FLeftItem <> nil then
      Result := Result and (GetNewLeftItemSize(ADelta) >= GetItemMinSize(FLeftItem.ViewInfo)) and (GetNewLeftItemSize(ADelta) <= GetItemMaxSize(FLeftItem.ViewInfo));
    if Result and (FRightItem <> nil) then
      Result := Result and (GetNewRightItemSize(ADelta) >= GetItemMinSize(FRightItem.ViewInfo)) and (GetNewRightItemSize(ADelta) <= GetItemMaxSize(FRightItem.ViewInfo));
  end;

var
  ADelta: Integer;
begin
  if not cxPointIsEqual(FPrevPoint, P) then
  begin
    FPrevPoint := P;
    ADelta := GetSignificantValue(P) - GetSignificantValue(FStartDragPoint);
    case FSizingStrategy of
      lssLeft:
        SetItemSize(FLeftItem.ViewInfo, GetNewLeftItemSize(ADelta));
      lssRight:
        SetItemSize(FRightItem.ViewInfo, GetNewRightItemSize(ADelta));
      lssClient:
        begin
          CorrectDelta(ADelta);
          if CanResize(ADelta) then
          begin
            Container.BeginUpdate;
            try
              SetItemSize(FLeftItem.ViewInfo, GetNewLeftItemSize(ADelta));
              SetItemSize(FRightItem.ViewInfo, GetNewRightItemSize(ADelta));
            finally
              Container.CancelUpdate;
              TdxLayoutSizeOptionsAccess(FLeftItem.SizeOptions).Changed;
            end;
          end;
        end;
    end;
    Container.Update;
  end;
end;

procedure TdxLayoutSplitterDragAndDropObject.Init(ASource: TdxLayoutDragSource; ASourceItem: TdxCustomLayoutItem; const P: TPoint);

  function GetLeftItem: TdxCustomLayoutItem;
  var
    I: Integer;
  begin
    Result := nil;
    for I := ASourceItem.VisibleIndex - 1 downto 0 do
      if GetWorkAlign(FParentItem.VisibleItems[I].ViewInfo) in [GetWorkAlign(FSourceItem.ViewInfo), ahClient] then
      begin
        Result := FParentItem.VisibleItems[I];
        Break;
      end;
  end;

  function GetRightItem: TdxCustomLayoutItem;
  var
    I: Integer;
  begin
    Result := nil;
    for I := ASourceItem.VisibleIndex + 1 to FParentItem.VisibleCount - 1 do
      if GetWorkAlign(FParentItem.VisibleItems[I].ViewInfo) in [GetWorkAlign(FSourceItem.ViewInfo), ahClient] then
      begin
        Result := FParentItem.VisibleItems[I];
        Break;
      end;
  end;

  function GetSimpleStrategy(AAlign: TdxLayoutAlignHorz): TdxLayoutSizingStrategy;
  begin
    case AAlign of
      ahLeft:
        if FLeftItem <> nil then
          Result := lssLeft
        else
          Result := lssNone;
      ahRight:
        if FRightItem <> nil then
          Result := lssRight
        else
          Result := lssNone;
    else
      Result := lssNone;
    end;
    if (Result in [lssRight, lssLeft]) and (FLeftItem <> nil) and (FRightItem <> nil) and
      (GetWorkAlign(FLeftItem.ViewInfo) = ahClient) and (GetWorkAlign(FRightItem.ViewInfo) = ahClient) then
      Result := lssClient;
  end;

var
  I: Integer;
begin
  inherited;

  FParentItem := ASourceItem.Parent;

  SetLength(FOriginalSizes, FParentItem.VisibleCount);
  for I := 0 to FParentItem.VisibleCount - 1 do
    FOriginalSizes[I] := Point(FParentItem.VisibleItems[I].Width, FParentItem.VisibleItems[I].Height);

  FLeftItem := GetLeftItem;
  FRightItem := GetRightItem;
  if FLeftItem <> nil then
    FLeftItemSize := GetItemSize(FLeftItem.ViewInfo);
  if FRightItem <> nil then
    FRightItemSize := GetItemSize(FRightItem.ViewInfo);

  case GetWorkAlign(FSourceItem.ViewInfo) of
    ahLeft:
      if (FLeftItem <> nil) and (GetWorkAlign(FLeftItem.ViewInfo) = ahClient) then
        FSizingStrategy := GetSimpleStrategy(ahRight)
      else
        FSizingStrategy := GetSimpleStrategy(ahLeft);
    ahRight:
      if (FRightItem <> nil) and (GetWorkAlign(FRightItem.ViewInfo) = ahClient) then
        FSizingStrategy := GetSimpleStrategy(ahLeft)
      else
        FSizingStrategy := GetSimpleStrategy(ahRight);
  else
    FSizingStrategy := lssNone;
  end;

  if FSizingStrategy = lssClient then
  begin
    Container.BeginUpdate;
    try
      for I := 0 to FParentItem.VisibleCount - 1 do
        if GetWorkAlign(FParentItem.VisibleItems[I].ViewInfo) = ahClient then
          SetItemSize(FParentItem.VisibleItems[I].ViewInfo, GetItemSize(FParentItem.VisibleItems[I].ViewInfo));
    finally
      Container.CancelUpdate;
    end;
  end;

  FPrevPoint := P;
end;

procedure TdxLayoutSplitterDragAndDropObject.RestoreSize;
var
  I: Integer;
begin
  Container.BeginUpdate;
  try
    for I := 0 to FParentItem.VisibleCount - 1 do
      SetItemSize(FParentItem.VisibleItems[I].ViewInfo, GetSignificantValue(FOriginalSizes[I]));
  finally
    Container.EndUpdate(False);
  end;
end;

{ TdxLayoutHSplitterDragAndDropObject }

function TdxLayoutHSplitterDragAndDropObject.GetWorkAlign(AViewInfo: TdxCustomLayoutItemViewInfo): TdxLayoutAlignHorz;
begin
  Result := AViewInfo.AlignHorz;
end;

function TdxLayoutHSplitterDragAndDropObject.GetItemMaxSize(AViewInfo: TdxCustomLayoutItemViewInfo): Integer;
begin
  Result := AViewInfo.MaxWidth;
end;

function TdxLayoutHSplitterDragAndDropObject.GetItemMinSize(AViewInfo: TdxCustomLayoutItemViewInfo): Integer;
begin
  Result := AViewInfo.MinWidth;
end;

function TdxLayoutHSplitterDragAndDropObject.GetItemSize(AViewInfo: TdxCustomLayoutItemViewInfo): Integer;
begin
  Result := cxRectWidth(AViewInfo.Bounds);
end;

function TdxLayoutHSplitterDragAndDropObject.GetSignificantValue(const P: TPoint): Integer;
begin
  Result := P.X;
end;

procedure TdxLayoutHSplitterDragAndDropObject.SetItemSize(AViewInfo: TdxCustomLayoutItemViewInfo; ASize: Integer);
begin
  ASize := Max(ASize, 1);
  TdxCustomLayoutItemViewInfoAccess(AViewInfo).Item.Width := ASize;
end;

{ TdxLayoutHSplitterDragAndDropObject }

function TdxLayoutVSplitterDragAndDropObject.GetWorkAlign(AViewInfo: TdxCustomLayoutItemViewInfo): TdxLayoutAlignHorz;
begin
  Result := TdxLayoutAlignHorz(AViewInfo.AlignVert);
end;

function TdxLayoutVSplitterDragAndDropObject.GetItemMaxSize(AViewInfo: TdxCustomLayoutItemViewInfo): Integer;
begin
  Result := AViewInfo.MaxHeight;
end;

function TdxLayoutVSplitterDragAndDropObject.GetItemMinSize(AViewInfo: TdxCustomLayoutItemViewInfo): Integer;
begin
  Result := AViewInfo.MinHeight;
end;

function TdxLayoutVSplitterDragAndDropObject.GetItemSize(AViewInfo: TdxCustomLayoutItemViewInfo): Integer;
begin
  Result := cxRectHeight(AViewInfo.Bounds);
end;

function TdxLayoutVSplitterDragAndDropObject.GetSignificantValue(const P: TPoint): Integer;
begin
  Result := P.Y;
end;

procedure TdxLayoutVSplitterDragAndDropObject.SetItemSize(AViewInfo: TdxCustomLayoutItemViewInfo; ASize: Integer);
begin
  ASize := Max(ASize, 1);
  TdxCustomLayoutItemViewInfoAccess(AViewInfo).Item.Height := ASize;
end;

{ TdxLayoutDragAndDropHelper }

constructor TdxLayoutDragAndDropHelper.Create(AContainer: TdxLayoutContainer);
begin
  inherited Create;
  FContainer := AContainer;
  Reset;
end;

procedure TdxLayoutDragAndDropHelper.InitializeDragItem(AItem: TdxCustomLayoutItem;
  X, Y: Integer);
begin
  FMouseDownPos := Point(X, Y);
  FDragItem := AItem;
end;

procedure TdxLayoutDragAndDropHelper.Reset;
begin
  FDragItem := nil;
  FMouseDownPos := cxInvalidPoint;
end;

procedure TdxLayoutDragAndDropHelper.TryBeginDragAndDrop(X, Y: Integer;
  ASource: TdxLayoutDragSource);
begin
  if CanBeginDragAndDrop(X, Y) then
    BeginDragAndDrop(ASource);
end;

function TdxLayoutDragAndDropHelper.CanBeginDragAndDrop(X, Y: Integer): Boolean;
begin
  Result := Container.CanDragAndDrop and (Control.DragAndDropState = ddsNone) and not
    (cxPointIsEqual(FMouseDownPos, cxInvalidPoint) or IsPointInDragDetectArea(FMouseDownPos, X, Y)) and
    (DragItem <> nil) and TdxCustomLayoutItemAccess(DragItem).CanDragAndDrop(Point(X, Y));
end;

procedure TdxLayoutDragAndDropHelper.BeginDragAndDrop(ASource: TdxLayoutDragSource);
begin
  DragAndDropObject.Init(ASource, DragItem, GetMouseCursorPos);
  Control.BeginDragAndDrop;
end;

function TdxLayoutDragAndDropHelper.GetDragAndDropObject: TdxLayoutDragAndDropObject;
begin
  Result := TdxLayoutDragAndDropObject(Control.DragAndDropObject);
end;

function TdxLayoutDragAndDropHelper.GetControl: TcxControl;
begin
  Result := Container.ItemsParent;
end;

{ TdxLayoutItemDropPlaceWindow }

procedure TdxLayoutItemDropPlaceWindow.CreateParams(var Params: TCreateParams);
begin
  inherited CreateParams(Params);
  with Params do
  begin
    Style := WS_POPUP;
    ExStyle := WS_EX_TOOLWINDOW or WS_EX_TOPMOST;
  end;
end;

procedure TdxLayoutItemDropPlaceWindow.CreateWindowHandle(
  const Params: TCreateParams);
begin
  inherited;
  cxSetLayeredWindowAttributes(Handle, 153);
  SetWindowRgn(Handle, CreateRoundRectRgn(0, 0, Width+1, Height+1, 3, 3), False);
end;

procedure TdxLayoutItemDropPlaceWindow.Paint;
var
  ARgn: HRGN;
begin
  if FIsActive then
    Canvas.Brush.Color := clRed
  else
    Canvas.Brush.Color := $8080FF;
  Canvas.FillRect(ClientRect);
  ARgn := CreateRectRgn(0, 0, 0, 0);
  GetWindowRgn(Handle, ARgn);
  Canvas.Brush.Color := clRed;
  FrameRgn(Canvas.Handle, ARgn, Canvas.Brush.Handle, 1, 1);
  DeleteObject(ARgn);
end;

procedure TdxLayoutItemDropPlaceWindow.SetInControl(const Value: Boolean);
begin
  FInControl := Value;
  Invalidate;
end;

procedure TdxLayoutItemDropPlaceWindow.SetIsActive(Value: Boolean);
begin
  if FIsActive <> Value then
  begin
    FIsActive := Value;
    Invalidate;
  end;
end;

procedure TdxLayoutItemDropPlaceWindow.WMNCHitTest(var Message: TWMNCHitTest);
begin
  inherited;
//  Message.Result := HTTRANSPARENT;
end;

{ TdxLayoutItemDropPlaceWindows }

procedure TdxLayoutItemDropPlaceWindows.Add(const R: TRect; ADestinationItem: TdxCustomLayoutItem);
var
  AWindow: TdxLayoutItemDropPlaceWindow;
begin
  AWindow := TdxLayoutItemDropPlaceWindow.Create(nil);
  AWindow.BoundsRect := R;
  AWindow.FDestinationItem := ADestinationItem;
  inherited Add(AWindow);
end;

procedure TdxLayoutItemDropPlaceWindows.Clear;
begin
  Visible := False;
  inherited Clear;
end;

function TdxLayoutItemDropPlaceWindows.GetItem(Index: Integer): TdxLayoutItemDropPlaceWindow;
begin
  Result := TdxLayoutItemDropPlaceWindow(inherited Items[Index]);
end;

procedure TdxLayoutItemDropPlaceWindows.SetVisible(const Value: Boolean);
var
  I: Integer;
begin
  if FVisible <> Value then
  begin
    FVisible := Value;
    for I := 0 to Count - 1 do
      if Visible then
        ShowWindow(Items[I].Handle, SW_SHOWNA)
      else
        ShowWindow(Items[I].Handle, SW_HIDE);
  end;
end;

function TdxLayoutItemDropPlaceWindows.GetActivePlace: TdxLayoutItemDropPlaceWindow;
var
  I: Integer;
begin
  Result := nil;
  for I := 0 to Count - 1 do
    if Items[I].FIsActive then
    begin
      Result := Items[I];
      Break; 
    end;
end;

procedure TdxLayoutItemDropPlaceWindows.SetActivePlace(Value: TdxLayoutItemDropPlaceWindow);
var
  AActivePlace: TdxLayoutItemDropPlaceWindow;
begin
  AActivePlace := GetActivePlace;
  if AActivePlace <> Value then
  begin
    if AActivePlace <> nil then
      AActivePlace.SetIsActive(False);
    if Value <> nil then
      Value.SetIsActive(True);
  end;
end;

{ TdxLayoutCustomDragAndDropController }

constructor TdxLayoutCustomDragAndDropController.Create(AOwner: TdxLayoutDragAndDropObject);
begin
  inherited Create;
  FOwner := AOwner;
  CreateDestinationImage;
end;

destructor TdxLayoutCustomDragAndDropController.Destroy;
begin
  DestroyDestinationImage;
  inherited;
end;

procedure TdxLayoutCustomDragAndDropController.Drop(Accepted: Boolean);

  function CanProcessDrop: Boolean;
  begin
    Result := Accepted and (DestinationContainer <> nil) and ((CanDrop and (DestinationItem <> nil)) or CanRemove);
  end;

  function GetDropActionType(AAreaPart: TdxLayoutDropAreaPart): TdxLayoutActionType;
  begin
    if AAreaPart in [apBefore, apAfter, apFirstChild, apLastChild] then
      Result := atInsert
    else
      Result := TdxLayoutGroupViewInfoAccess(DestinationGroup.ViewInfo).GetDropActionType(AAreaPart);
  end;

var
  ALinkSelf: TcxObjectLink;
begin
  FreeAndNil(FDragImage);
  UpdateStates;
  Changed;
  if CanProcessDrop then
  begin
    DestinationContainer.SaveToUndo;
    DestinationContainer.BeginUpdate;
    Container.SaveToUndo;
    Container.BeginUpdate;
    try
      if CanDrop and (DestinationGroup <> nil) then
        DoDrop(GetDropActionType(DropAreaPart))
      else
        if CanRemove then
          SourceItem.Parent := nil;
    finally
      ALinkSelf := cxAddObjectLink(SourceItem);
      try
        ResetDragAndDropObjects;
        Container.EndUpdate;
        DestinationContainer.EndUpdate;
        if ALinkSelf.Ref <> nil then
          (ALinkSelf.Ref as TdxCustomLayoutItem).MakeVisible;
      finally
        cxRemoveObjectLink(ALinkSelf);
      end;
    end;
    Container.Update;
    if DestinationContainer <> Container then
      DestinationContainer.Update;
    Owner.Modified;
  end
  else
    ResetDragAndDropObjects;
end;

procedure TdxLayoutCustomDragAndDropController.Calculate(const P: TPoint; var Accepted: Boolean);
begin
  UpdateStates;
  ShowDragImage;
  Accepted := DropAreaPart <> apNone;
  if FNeedUpdateDestinationImage then
  begin
    FDestinationImage.Visible := False;
    UpdateDestinationImage;
    FNeedUpdateDestinationImage := False;
  end;
end;

function TdxLayoutCustomDragAndDropController.CanDrop: Boolean;
begin
  Result := (DestinationGroup <> nil) and SourceItem.CanMoveTo(DestinationGroup) and
    not TdxLayoutGroupAccess(DestinationGroup).IsLocked and
    (not TdxLayoutContainerAccess(Container).IsQuickCustomization or TdxLayoutGroupAccess(DestinationGroup).CanQuickCustomized);
end;

function TdxLayoutCustomDragAndDropController.CanRemove: Boolean;
begin
  Result := (FHitTest.HitTestCode in [htAvailableItems, htNone]) and
    TdxCustomLayoutItemAccess(SourceItem).CanRemove and (SourceItem.Parent <> nil) and
    not TdxLayoutContainerAccess(Container).IsQuickCustomization;
end;

procedure TdxLayoutCustomDragAndDropController.TargetItemChanged;
begin
end;

procedure TdxLayoutCustomDragAndDropController.Changed;
begin
  Owner.Dirty := True;
end;

procedure TdxLayoutCustomDragAndDropController.CreateDestinationImage;
begin
  FDestinationImage := TcxDragImage.Create;
end;

procedure TdxLayoutCustomDragAndDropController.DoDrop(ALayoutAction: TdxLayoutActionType);

  function GetOrthogonalDirection: TdxLayoutDirection;
  begin
    Result := TdxLayoutGroupAccess(DestinationItem.Parent).GetHelperClass.GetOrthogonalDirection;
  end;

  function IsHorizontalDropAreaPart: Boolean;
  begin
    Result := DropAreaPart in [apLeft, apRight, apAfter, apBefore];
  end;

  procedure DoInsert;
  begin
    SourceItem.Move(DestinationGroup, GetDestinationPosition, True);
  end;

  procedure DoCreateGroup;
  const
    LayoutDirections: array[Boolean] of TdxLayoutDirection = (ldVertical, ldHorizontal);
  var
    AParentGroup: TdxLayoutGroup;
  begin
    if DestinationItem.IsRoot then
    begin
      DestinationGroup.PutChildrenIntoHiddenGroup;
      DestinationGroup.LayoutDirection := LayoutDirections[IsHorizontalDropAreaPart];
      AParentGroup := DestinationGroup;
    end
    else
      AParentGroup := DestinationItem.PutIntoHiddenGroup(GetOrthogonalDirection);
    TdxLayoutGroupAccess(AParentGroup).BuildVisibleItemsList;
    SourceItem.Move(AParentGroup, GetDestinationPosition, True);
  end;

  procedure DoContentInsert;
  begin
    DestinationGroup.PutChildrenIntoHiddenGroup;
    DestinationGroup.LayoutDirection := GetOrthogonalDirection;
    SourceItem.Move(DestinationGroup, GetDestinationPosition, True);
  end;

begin
  case ALayoutAction of
    atInsert:
      DoInsert;
    atCreateGroup:
      DoCreateGroup;
    atContentInsert:
      DoContentInsert;
  end;
end;

function TdxLayoutCustomDragAndDropController.FindDestLayoutContainer(const P: TPoint): TdxLayoutContainer;
var
  AContainer: IdxLayoutContainer;
  AControl: TWinControl;
  AParentForm: TCustomForm;
  AWnd: THandle;
begin
  Result := nil;
  if Container.IsDesigning then
  begin
    AWnd := cxWindowFromPoint(P);
    while (Result = nil) and (AWnd <> 0) do
    begin
      AControl := FindControl(AWnd);
      if Supports(AControl, IdxLayoutContainer, AContainer) then
      begin
        AParentForm := GetParentForm(AControl);
        if AParentForm is TdxLayoutControlCustomCustomizeForm then
          Result := TdxLayoutControlCustomCustomizeForm(AParentForm).Container
        else
        begin
          if (AControl.Owner = Container.ItemsParent.Owner) then
            Result := AContainer.GetContainer;
        end;
      end;
      AWnd := GetAncestor(AWnd, GA_PARENT);
    end;
  end;
  if Result = nil then
    Result := Container;
end;

function TdxLayoutCustomDragAndDropController.GetAreaPart(ADestinationItem: TdxCustomLayoutItem): TdxLayoutDropAreaPart;
begin
  if FHitTest.IsDropAreaPartDetermined then
    Result := FHitTest.GetDropAreaPart
  else
    if ADestinationItem <> nil then
      Result := TdxCustomLayoutItemViewInfoAccess(ADestinationItem.ViewInfo).GetDropAreaPart(FDestinationPoint)
    else
      Result := apNone;
end;

function TdxLayoutCustomDragAndDropController.GetDestinationGroup(ADestinationItem: TdxCustomLayoutItem): TdxLayoutGroup;
var
  ADropAreaPart: TdxLayoutDropAreaPart;
begin
  if ADestinationItem = nil then
    Result := nil
  else
  begin
    ADropAreaPart := GetAreaPart(ADestinationItem);
    if ADestinationItem.IsRoot or (ADropAreaPart in [apCenter, apFirstChild, apLastChild]) then
      Result := TdxLayoutGroup(ADestinationItem)
    else
      Result := ADestinationItem.Parent;
  end;
end;

function TdxLayoutCustomDragAndDropController.GetDestinationImageBounds: TRect;
begin
  Result := cxInvalidRect;
end;

function TdxLayoutCustomDragAndDropController.GetDestinationPosition: Integer;
begin
  case DropAreaPart of
    apRight, apBottom, apAfter:
      if DestinationItem = DestinationGroup then
        Result := DestinationGroup.Count
      else
        Result := DestinationItem.Index + 1;
    apLeft, apTop, apBefore:
      if DestinationItem = DestinationGroup then
        Result := 0
      else
        Result := DestinationItem.Index;
    apLastChild:
      Result := DestinationGroup.Count;
  else
    Result := 0;
  end;
  if (SourceItem.Parent = DestinationGroup) and (SourceItem.Index < Result) then
    Dec(Result);
end;

function TdxLayoutCustomDragAndDropController.GetFittedRect(const ARect: TRect): TRect;
begin
  cxRectIntersect(Result, ARect, DestinationContainer.ClientRect);
end;

procedure TdxLayoutCustomDragAndDropController.Invalidate;
begin
  FNeedUpdateDestinationImage := True;
end;

function TdxLayoutCustomDragAndDropController.GetCursor(Accepted: Boolean): TCursor;
begin
 if CanDrop then
    Result := crDefault
  else
    if CanRemove then
      Result := crdxLayoutControlRemove
    else
      Result := crdxLayoutControlNoDrop;
end;

procedure TdxLayoutCustomDragAndDropController.CreateDragImage;
begin
  FDragImage := TcxDragImage.Create;
  PaintDragImage;
end;

procedure TdxLayoutCustomDragAndDropController.DestroyDestinationImage;
begin
  FreeAndNil(FDestinationImage);
end;

procedure TdxLayoutCustomDragAndDropController.DetermineAreaPart;
begin
  DropAreaPart := GetAreaPart(TargetItem);
end;

procedure TdxLayoutCustomDragAndDropController.DetermineTargetItem;
begin
  TargetItem := FHitTest.GetDestinationItem;
end;

procedure TdxLayoutCustomDragAndDropController.DetermineDestinationItem;
var
  ADestinationItem: TdxCustomLayoutItem;
begin
  ADestinationItem := FTargetItem;
  DestinationItem := ADestinationItem;
  if (DestinationItem <> nil) and (DestinationItem.Container <> DestinationContainer) then
    DestinationContainer := DestinationItem.Container;
end;

procedure TdxLayoutCustomDragAndDropController.DetermineDestinationGroup;
begin
  DestinationGroup := GetDestinationGroup(DestinationItem);
end;

function TdxLayoutCustomDragAndDropController.GetContainer: TdxLayoutContainer;
begin
  Result := Owner.Container;
end;

function TdxLayoutCustomDragAndDropController.GetSourceItem: TdxCustomLayoutItem;
begin
  Result := Owner.SourceItem;
end;

procedure TdxLayoutCustomDragAndDropController.PaintDragImage;
var
  AViewInfo: TdxCustomLayoutItemViewInfo;

  function GetItemDragBounds: TRect;
  begin
    Result := AViewInfo.SelectionBorderRect;
  end;

  procedure CalculateDragImageOffset;
  begin
    FDragImageOffset := cxNullPoint;
  end;

  function GetViewInfoBounds(AHasBorderChanged: Boolean): TRect;
  begin
    if TdxCustomLayoutItemAccess(SourceItem).IsAvailable or not TdxCustomLayoutItemViewInfoAccess(AViewInfo).ActuallyVisible then
      Result := Rect(-TdxCustomLayoutItemViewInfoAccess(AViewInfo).CalculateWidth, -TdxCustomLayoutItemViewInfoAccess(AViewInfo).CalculateHeight, 0, 0)
    else
    begin
      Result := TdxCustomLayoutItemViewInfoAccess(AViewInfo).OriginalBounds;
      if AHasBorderChanged then
        Result := TdxLayoutGroupViewInfoAccess(AViewInfo).GetItemAreaBounds(Result);
   end;
  end;

var
  ANeedDestroyViewInfo: Boolean;
  APrevHasBorder: Boolean;
  APrevBounds: TRect;
begin
  if not TdxCustomLayoutItemAccess(SourceItem).ActuallyVisible then
  begin
    AViewInfo := TdxCustomLayoutItemAccess(SourceItem).GetViewInfoClass.Create(Container.ViewInfo, nil, SourceItem);
    ANeedDestroyViewInfo := True;
  end
  else
  begin
    AViewInfo := SourceItem.ViewInfo;
    ANeedDestroyViewInfo := False;
    APrevBounds := AViewInfo.Bounds;
  end;
  try
    APrevHasBorder := TdxCustomLayoutItemViewInfoAccess(AViewInfo).HasBorder;
    Container.ViewInfo.StartDragImagePainting;
    try
      AViewInfo.Calculate(GetViewInfoBounds(TdxCustomLayoutItemViewInfoAccess(AViewInfo).HasBorder <> APrevHasBorder));

      FDragImage.SetBounds(0, 0, cxRectWidth(GetItemDragBounds), cxRectHeight(GetItemDragBounds));
      FDragImage.Canvas.WindowOrg := GetItemDragBounds.TopLeft;
      try
        with TdxCustomLayoutItemViewInfoAccess(AViewInfo).GetPainterClass.Create(FDragImage.Canvas, AViewInfo) do
          try
            PaintDragImage;
          finally
            Free;
          end;
      finally
        FDragImage.Canvas.WindowOrg := cxNullPoint;
      end;
      CalculateDragImageOffset;
    finally
      Container.ViewInfo.FinishDragImagePainting;
    end;
  finally
    if ANeedDestroyViewInfo then
      AViewInfo.Destroy
    else
      AViewInfo.Calculate(APrevBounds);
  end;
end;

procedure TdxLayoutCustomDragAndDropController.PaintDestinationImageBackground;
const
  ABorderWidth = 1;
var
  R: TRect;
begin
  R := FDestinationImage.ClientRect;
  FDestinationImage.Canvas.FrameRect(R, dxLayoutDestinationBorderColor);
  InflateRect(R, -ABorderWidth, -ABorderWidth);
  FDestinationImage.Canvas.FillRect(R, dxLayoutDestinationColor);
end;

procedure TdxLayoutCustomDragAndDropController.PaintDestinationImageContent;
const
  ABorderWidth = 1;
var
  R: TRect;
  ATargetRect: TRect;
  AOffset: Integer;
begin
  ATargetRect := TdxLayoutGroupViewInfoAccess(DestinationGroup.ViewInfo).GetDropAreaPartBounds(DropAreaPart, DestinationItem.ViewInfo, dxLayoutThinPartWidth);
  R := GetFittedRect(DestinationGroup.ViewInfo.SelectionArea);
  ATargetRect := cxRectOffset(ATargetRect, -R.Left, -R.Top);
  R := FDestinationImage.ClientRect;
  AOffset := ABorderWidth + 1;
  R := cxRectInflate(R, -AOffset, -AOffset);
  if cxRectWidth(ATargetRect) < cxRectHeight(ATargetRect) then
  begin
    if ATargetRect.Left < R.Left then
      ATargetRect := cxRectOffset(ATargetRect, R.Left - ATargetRect.Left, 0);
    if ATargetRect.Right > R.Right then
      ATargetRect := cxRectOffset(ATargetRect, R.Right - ATargetRect.Right, 0);
  end
  else
  begin
    if ATargetRect.Top < R.Top then
      ATargetRect := cxRectOffset(ATargetRect, 0, R.Top - ATargetRect.Top);
    if ATargetRect.Bottom > R.Bottom then
      ATargetRect := cxRectOffset(ATargetRect, 0, R.Bottom - ATargetRect.Bottom);
  end;

  FDestinationImage.Canvas.FrameRect(ATargetRect, dxLayoutDestinationBorderColor);
end;

procedure TdxLayoutCustomDragAndDropController.PaintDestinationImage;
begin
  FDestinationImage.Canvas.SaveState;
  try
    PaintDestinationImageBackground;
    PaintDestinationImageContent;
  finally
    FDestinationImage.Canvas.RestoreState;
  end;

  FNeedUpdateDestinationImage := False;
end;

procedure TdxLayoutCustomDragAndDropController.UpdateDestinationImage;
var
  R: TRect;
begin
  if (DestinationItem = nil) or (DropAreaPart = apNone) or
    (FHitTest.HitTestCode in dxCustomizeFormHitTestCodes) then Exit;

  FDestinationImage.PopupParent := GetParentForm(DestinationContainer.ItemsParent);
  FDestinationImage.FormStyle := fsNormal;
  R := GetDestinationImageBounds;
  R.TopLeft := DestinationContainer.ClientToScreen(R.TopLeft);
  R.BottomRight := DestinationContainer.ClientToScreen(R.BottomRight);
  FDestinationImage.SetBounds(R.Left, R.Top, cxRectWidth(R), cxRectHeight(R));
  PaintDestinationImage;
  FDestinationImage.Visible := True;
  FDragImage.BringToFront;
end;

procedure TdxLayoutCustomDragAndDropController.ResetDragAndDropObjects;
begin
  Owner.SourceItem := nil;
  DestinationItem := nil;
  DestinationGroup := nil;
  FHitTest := nil;
  TdxLayoutContainerAccess(Container).CustomizeFormPostUpdate([cfutDragAndDropState]);
end;

procedure TdxLayoutCustomDragAndDropController.SetDropAreaPart(Value: TdxLayoutDropAreaPart);
begin
  if FDropAreaPart <> Value then
  begin
    FDropAreaPart := Value;
    TargetItemChanged; 
    Changed;
  end;
end;

procedure TdxLayoutCustomDragAndDropController.SetDestinationContainer(Value: TdxLayoutContainer);
begin
  if FDestinationContainer <> Value then
  begin
    FDestinationContainer := Value;
    Changed;
  end;
end;

procedure TdxLayoutCustomDragAndDropController.SetDestinationGroup(
  Value: TdxLayoutGroup);
begin
  if FDestinationGroup <> Value then
  begin
    FDestinationGroup := Value;
    Changed;
  end;
end;

procedure TdxLayoutCustomDragAndDropController.SetDestinationItem(Value: TdxCustomLayoutItem);
begin
  if (Value = SourceItem) or ((Value is TdxLayoutGroup) and not SourceItem.CanMoveTo(Value)) then
    Value := nil;

  if FDestinationItem <> Value then
  begin
    if FDestinationItem <> nil then
    begin
      if FHitTest.HitTestCode in dxCustomizeFormHitTestCodes then
        DestinationItem.Container.CustomizeForm.ToggleHotTrackState(DestinationItem);
    end;
    FDestinationItem := Value;
    if FDestinationItem <> nil then
    begin
      if FHitTest.HitTestCode in dxCustomizeFormHitTestCodes then
        DestinationItem.Container.CustomizeForm.ToggleHotTrackState(DestinationItem);
    end;
    Changed;
  end;
end;

procedure TdxLayoutCustomDragAndDropController.SetTargetItem(Value: TdxCustomLayoutItem);
begin
  if FTargetItem <> Value then
  begin
    FTargetItem := Value;
    TargetItemChanged;
    Changed;
  end;
end;

procedure TdxLayoutCustomDragAndDropController.ShowDragImage;
begin
  if FDragImage <> nil then
  begin
    FDragImage.MoveTo(FDragImagePoint);
    FDragImage.Visible := True;
  end;
end;

procedure TdxLayoutCustomDragAndDropController.UpdateStates;

  procedure DetermineDestinationContainer(AHitContainer: TdxLayoutContainer);
  begin
    if DestinationItem = nil then
      DestinationContainer := AHitContainer
    else
      DestinationContainer := DestinationItem.Container;
  end;

  procedure DetermineHitTest(AHitContainer: TdxLayoutContainer; const P: TPoint);
  begin
    FHitTest := AHitContainer.GetHitTest(AHitContainer.ScreenToClient(P));
  end;

var
  AHitContainer: TdxLayoutContainer;
  AScreenPoint: TPoint;
begin
  AScreenPoint := GetMouseCursorPos;
  AHitContainer := FindDestLayoutContainer(AScreenPoint);
  FDragImagePoint := cxPointOffset(AScreenPoint, FDragImageOffset);
  FDestinationPoint := AHitContainer.ScreenToClient(AScreenPoint);

  DetermineHitTest(AHitContainer, AScreenPoint);
  DetermineTargetItem;
  DetermineDestinationItem;
  DetermineDestinationContainer(AHitContainer);

  DetermineAreaPart;
  DetermineDestinationGroup;
end;

{ TdxLayoutSingleDragAndDropController }

function TdxLayoutSingleDragAndDropController.GetDestinationImageBounds: TRect;
begin
  if DestinationGroup = nil then
    Result := cxInvalidRect
  else
    Result := GetFittedRect(DestinationGroup.ViewInfo.SelectionArea);
end;

{ TdxLayoutMultiDragAndDropController }

constructor TdxLayoutMultiDragAndDropController.Create(AOwner: TdxLayoutDragAndDropObject);
begin
  inherited;
  FDropPlaces := TdxLayoutItemDropPlaceWindows.Create;
end;

destructor TdxLayoutMultiDragAndDropController.Destroy;
begin
  FreeAndNil(FDropPlaces);
  inherited;
end;

function TdxLayoutMultiDragAndDropController.GetDestinationImageBounds: TRect;
begin
  if DestinationItem = nil then
    Result := cxInvalidRect
  else
    Result := GetFittedRect(DestinationItem.ViewInfo.Bounds);
end;

function TdxLayoutMultiDragAndDropController.GetScreenRect(const R: TRect): TRect;
begin
  if cxRectWidth(R) > cxRectHeight(R) then
  begin
    Result.Left := Max(R.Left, DestinationContainer.ClientRect.Left);
    Result.Top := R.Top;
    Result.Right := Min(R.Right, DestinationContainer.ClientRect.Right);
    Result.Bottom := R.Bottom;
  end
  else
  begin
    Result.Left := R.Left;
    Result.Top := Max(R.Top, DestinationContainer.ClientRect.Top);
    Result.Right := R.Right;
    Result.Bottom := Min(R.Bottom, DestinationContainer.ClientRect.Bottom);
  end;

  MoveRectsApart(DestinationContainer.ClientRect, Result);

  Result.TopLeft := DestinationContainer.ClientToScreen(Result.TopLeft);
  Result.BottomRight := DestinationContainer.ClientToScreen(Result.BottomRight);
end;

procedure TdxLayoutMultiDragAndDropController.MoveRectsApart(ADirection: TcxDirection; const ABaseRect: TRect; var AMovedRect: TRect);
begin
  case ADirection of
    dirRight:
      AMovedRect := cxRectOffset(AMovedRect, ABaseRect.Left - AMovedRect.Right, 0);
    dirDown:
      AMovedRect := cxRectOffset(AMovedRect, 0, ABaseRect.Top - AMovedRect.Bottom);
    dirLeft:
      AMovedRect := cxRectOffset(AMovedRect, ABaseRect.Right - AMovedRect.Left, 0);
    dirUp:
      AMovedRect := cxRectOffset(AMovedRect, 0, ABaseRect.Bottom - AMovedRect.Top);
  end;
end;

procedure TdxLayoutMultiDragAndDropController.MoveRectsApart(const ABaseRect: TRect; var AMovedRect: TRect);
begin
  if AMovedRect.Left > ABaseRect.Right then
    MoveRectsApart(dirLeft, ABaseRect, AMovedRect);
  if AMovedRect.Right < ABaseRect.Left then
    MoveRectsApart(dirRight, ABaseRect, AMovedRect);
  if AMovedRect.Top > ABaseRect.Bottom then
    MoveRectsApart(dirUp, ABaseRect, AMovedRect);
  if AMovedRect.Bottom < ABaseRect.Top then
    MoveRectsApart(dirDown, ABaseRect, AMovedRect);
end;

procedure TdxLayoutMultiDragAndDropController.PaintDestinationImageContent;
begin
//do nothing
end;

// APlaceColor = $8080FF;
procedure TdxLayoutMultiDragAndDropController.UpdateDestinationImage;
begin
  inherited;
  if (DestinationItem <> nil) and (DropPlaces.Count > 0) then
    DropPlaces.Visible := True;
end;

procedure TdxLayoutMultiDragAndDropController.TargetItemChanged;

  function cxRectNear(const R1, R2: TRect; AOffset: Integer = 1): Boolean;
  begin
    Result := cxRectIntersect(cxRectInflate(R1, AOffset, AOffset), R2);
  end;

const
  ABorderWidth = 1;
  ADirectionMap: array [TdxLayoutDropAreaPart] of TcxDirection = (dirNone, dirRight, dirDown, dirLeft, dirUp, dirNone, dirNone, dirNone, dirNone, dirNone, dirNone, dirNone);
var
  ANewTargetRect, APrevTargetRect, ADropAreaPartBounds: TRect;
  ATargetItem: TdxCustomLayoutItem;
  ATargetGroup: TdxLayoutGroup;
begin
  DropPlaces.Clear;

  if (TargetItem = nil) or (FHitTest.HitTestCode in dxCustomizeFormHitTestCodes) then
    Exit;

  ATargetItem := TargetItem;
  APrevtargetRect := cxInvalidRect;

  while ATargetItem <> nil do
  begin
    if DropAreaPart = TdxCustomLayoutItemViewInfoAccess(ATargetItem.ViewInfo).GetDropAreaPart(FDestinationPoint) then
    begin
      ATargetGroup := GetDestinationGroup(ATargetItem);

      ADropAreaPartBounds := TdxLayoutGroupViewInfoAccess(ATargetGroup.ViewInfo).GetDropAreaPartBounds(DropAreaPart, ATargetItem.ViewInfo, dxLayoutThickPartWidth);
      ANewTargetRect := GetScreenRect(ADropAreaPartBounds);

      if cxRectIsEqual(APrevTargetRect, cxInvalidRect) or cxRectNear(ANewTargetRect, APrevTargetRect, 3) then
      begin
        if not cxRectIsEqual(APrevTargetRect, cxInvalidRect) then
          MoveRectsApart(ADirectionMap[DropAreaPart], APrevTargetRect, ANewTargetRect);

        APrevTargetRect := ANewTargetRect;
        DropPlaces.Add(ANewTargetRect, ATargetItem);
      end;
    end;
    ATargetItem := ATargetItem.Parent;
  end;

  Invalidate;
end;

procedure TdxLayoutMultiDragAndDropController.DetermineAreaPart;
var
  ACode: Integer;
begin
  inherited;

  ACode := GetAreaPartCode;
  if FAreaPartCode <> ACode then
  begin
    FAreaPartCode := ACode;
    TargetItemChanged;
  end;
end;

procedure TdxLayoutMultiDragAndDropController.DetermineTargetItem;
begin
  if GetDropPlaceWindow(GetMouseCursorPos) = nil then
    inherited;
end;

procedure TdxLayoutMultiDragAndDropController.DetermineDestinationItem;
begin
  if FHitTest.HitTestCode in dxCustomizeFormHitTestCodes then
    inherited
  else
    //do nothing
end;

procedure TdxLayoutMultiDragAndDropController.DetermineDestinationGroup;
var
  AActiveDropPlace: TdxLayoutItemDropPlaceWindow;
  ADestinationItem: TdxCustomLayoutItem;
begin
  if FHitTest.HitTestCode in dxCustomizeFormHitTestCodes then
    inherited
  else
  begin
    if DropPlaces.Count > 0 then
    begin
      AActiveDropPlace := GetDropPlaceWindow(GetMouseCursorPos);
      if AActiveDropPlace = nil then
        AActiveDropPlace := DropPlaces.Items[0];
      DropPlaces.ActivePlace := AActiveDropPlace;
      ADestinationItem := AActiveDropPlace.FDestinationItem;
    end
    else
      ADestinationItem := nil;

    DestinationItem := ADestinationItem;
    DestinationGroup := GetDestinationGroup(DestinationItem);
  end;
end;

function TdxLayoutMultiDragAndDropController.GetAreaPartCode: Integer;
var
  AAreaPart: TdxLayoutDropAreaPart;
  AItem: TdxCustomLayoutItem;
begin
  Result := 0;
  AItem := TargetItem;

  while (AItem <> nil) and (AItem.ViewInfo <> nil) do
  begin
    AAreaPart := TdxCustomLayoutItemViewInfoAccess(AItem.ViewInfo).GetDropAreaPart(FDestinationPoint);
    AItem := AItem.Parent;
    Result := Result * 10 + Integer(AAreaPart);
  end;
end;

function TdxLayoutMultiDragAndDropController.GetDropPlaceWindow(const P: TPoint): TdxLayoutItemDropPlaceWindow;
var
  I: Integer;
begin
  Result := nil;
  for I := 0 to DropPlaces.Count - 1 do
  begin
    if cxRectPtIn(DropPlaces.Items[I].BoundsRect, P) then
    begin
      Result := DropPlaces.Items[I];
      Break;
    end;
  end;
end;

initialization
  Screen.Cursors[crdxLayoutControlNoDrop] := LoadCursor(HInstance, 'DXLAYOUTCONTROLNODROP');
  Screen.Cursors[crdxLayoutControlRemove] := LoadCursor(HInstance, 'DXLAYOUTCONTROLREMOVE');

end.
